/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: XObject.java,v 1.2.4.1 2005/09/14 20:34:45 jeffsuttor Exp $
 */
package com.sun.org.apache.xpath.internal.objects;

import java.io.Serializable;

import com.sun.org.apache.xalan.internal.res.XSLMessages;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMIterator;
import com.sun.org.apache.xml.internal.utils.XMLString;
import com.sun.org.apache.xpath.internal.Expression;
import com.sun.org.apache.xpath.internal.ExpressionOwner;
import com.sun.org.apache.xpath.internal.NodeSetDTM;
import com.sun.org.apache.xpath.internal.XPathContext;
import com.sun.org.apache.xpath.internal.XPathException;
import com.sun.org.apache.xpath.internal.XPathVisitor;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;

import org.w3c.dom.DocumentFragment;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.NodeIterator;

/**
 * This class represents an XPath object, and is capable of
 * converting the object to various types, such as a string.
 * This class acts as the base class to other XPath type objects,
 * such as XString, and provides polymorphic casting capabilities.
 *
 * @xsl.usage general
 */
public class XObject extends Expression implements Serializable, Cloneable {

  static final long serialVersionUID = -821887098985662951L;

  /**
   * The java object which this object wraps.
   *
   * @serial
   */
  protected Object m_obj;  // This may be NULL!!!

  /**
   * Create an XObject.
   */
  public XObject() {
  }

  /**
   * Create an XObject.
   *
   * @param obj Can be any object, should be a specific type for derived classes, or null.
   */
  public XObject(Object obj) {
    setObject(obj);
  }

  protected void setObject(Object obj) {
    m_obj = obj;
  }

  /**
   * For support of literal objects in xpaths.
   *
   * @param xctxt The XPath execution context.
   * @return This object.
   */
  public XObject execute(XPathContext xctxt)
      throws javax.xml.transform.TransformerException {
    return this;
  }

  /**
   * Specify if it's OK for detach to release the iterator for reuse.
   * This function should be called with a value of false for objects that are
   * stored in variables.
   * Calling this with a value of false on a XNodeSet will cause the nodeset
   * to be cached.
   *
   * @param allowRelease true if it is OK for detach to release this iterator for pooling.
   */
  public void allowDetachToRelease(boolean allowRelease) {
  }

  /**
   * Detaches the <code>DTMIterator</code> from the set which it iterated
   * over, releasing any computational resources and placing the iterator
   * in the INVALID state. After <code>detach</code> has been invoked,
   * calls to <code>nextNode</code> or <code>previousNode</code> will
   * raise a runtime exception.
   */
  public void detach() {
  }

  /**
   * Forces the object to release it's resources.  This is more harsh than
   * detach().
   */
  public void destruct() {

    if (null != m_obj) {
      allowDetachToRelease(true);
      detach();

      setObject(null);
    }
  }

  /**
   * Reset for fresh reuse.
   */
  public void reset() {
  }

  /**
   * Directly call the
   * characters method on the passed ContentHandler for the
   * string-value. Multiple calls to the
   * ContentHandler's characters methods may well occur for a single call to
   * this method.
   *
   * @param ch A non-null reference to a ContentHandler.
   */
  public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch)
      throws org.xml.sax.SAXException {
    xstr().dispatchCharactersEvents(ch);
  }

  /**
   * Create the right XObject based on the type of the object passed.  This
   * function can not make an XObject that exposes DOM Nodes, NodeLists, and
   * NodeIterators to the XSLT stylesheet as node-sets.
   *
   * @param val The java object which this object will wrap.
   * @return the right XObject based on the type of the object passed.
   */
  static public XObject create(Object val) {
    return XObjectFactory.create(val);
  }

  /**
   * Create the right XObject based on the type of the object passed.
   * This function <emph>can</emph> make an XObject that exposes DOM Nodes, NodeLists, and
   * NodeIterators to the XSLT stylesheet as node-sets.
   *
   * @param val The java object which this object will wrap.
   * @param xctxt The XPath context.
   * @return the right XObject based on the type of the object passed.
   */
  static public XObject create(Object val, XPathContext xctxt) {
    return XObjectFactory.create(val, xctxt);
  }

  /**
   * Constant for NULL object type
   */
  public static final int CLASS_NULL = -1;

  /**
   * Constant for UNKNOWN object type
   */
  public static final int CLASS_UNKNOWN = 0;

  /**
   * Constant for BOOLEAN  object type
   */
  public static final int CLASS_BOOLEAN = 1;

  /**
   * Constant for NUMBER object type
   */
  public static final int CLASS_NUMBER = 2;

  /**
   * Constant for STRING object type
   */
  public static final int CLASS_STRING = 3;

  /**
   * Constant for NODESET object type
   */
  public static final int CLASS_NODESET = 4;

  /**
   * Constant for RESULT TREE FRAGMENT object type
   */
  public static final int CLASS_RTREEFRAG = 5;

  /**
   * Represents an unresolved variable type as an integer.
   */
  public static final int CLASS_UNRESOLVEDVARIABLE = 600;

  /**
   * Tell what kind of class this is.
   *
   * @return CLASS_UNKNOWN
   */
  public int getType() {
    return CLASS_UNKNOWN;
  }

  /**
   * Given a request type, return the equivalent string.
   * For diagnostic purposes.
   *
   * @return type string "#UNKNOWN" + object class name
   */
  public String getTypeString() {
    return "#UNKNOWN (" + object().getClass().getName() + ")";
  }

  /**
   * Cast result object to a number. Always issues an error.
   *
   * @return 0.0
   */
  public double num() throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a number");

    return 0.0;
  }

  /**
   * Cast result object to a number, but allow side effects, such as the
   * incrementing of an iterator.
   *
   * @return numeric value of the string conversion from the next node in the NodeSetDTM, or NAN if
   * no node was found
   */
  public double numWithSideEffects() throws javax.xml.transform.TransformerException {
    return num();
  }

  /**
   * Cast result object to a boolean. Always issues an error.
   *
   * @return false
   */
  public boolean bool() throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a number");

    return false;
  }

  /**
   * Cast result object to a boolean, but allow side effects, such as the
   * incrementing of an iterator.
   *
   * @return True if there is a next node in the nodeset
   */
  public boolean boolWithSideEffects() throws javax.xml.transform.TransformerException {
    return bool();
  }


  /**
   * Cast result object to a string.
   *
   * @return The string this wraps or the empty string if null
   */
  public XMLString xstr() {
    return XMLStringFactoryImpl.getFactory().newstr(str());
  }

  /**
   * Cast result object to a string.
   *
   * @return The object as a string
   */
  public String str() {
    return (m_obj != null) ? m_obj.toString() : "";
  }

  /**
   * Return the string representation of the object
   *
   * @return the string representation of the object
   */
  public String toString() {
    return str();
  }

  /**
   * Cast result object to a result tree fragment.
   *
   * @param support XPath context to use for the conversion
   * @return the objec as a result tree fragment.
   */
  public int rtf(XPathContext support) {

    int result = rtf();

    if (DTM.NULL == result) {
      DTM frag = support.createDocumentFragment();

      // %OPT%
      frag.appendTextChild(str());

      result = frag.getDocument();
    }

    return result;
  }

  /**
   * Cast result object to a result tree fragment.
   *
   * @param support XPath context to use for the conversion
   * @return the objec as a result tree fragment.
   */
  public DocumentFragment rtree(XPathContext support) {
    DocumentFragment docFrag = null;
    int result = rtf();

    if (DTM.NULL == result) {
      DTM frag = support.createDocumentFragment();

      // %OPT%
      frag.appendTextChild(str());

      docFrag = (DocumentFragment) frag.getNode(frag.getDocument());
    } else {
      DTM frag = support.getDTM(result);
      docFrag = (DocumentFragment) frag.getNode(frag.getDocument());
    }

    return docFrag;
  }


  /**
   * For functions to override.
   *
   * @return null
   */
  public DocumentFragment rtree() {
    return null;
  }

  /**
   * For functions to override.
   *
   * @return null
   */
  public int rtf() {
    return DTM.NULL;
  }

  /**
   * Return a java object that's closest to the representation
   * that should be handed to an extension.
   *
   * @return The object that this class wraps
   */
  public Object object() {
    return m_obj;
  }

  /**
   * Cast result object to a nodelist. Always issues an error.
   *
   * @return null
   */
  public DTMIterator iter() throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a NodeList!");

    return null;
  }

  /**
   * Get a fresh copy of the object.  For use with variables.
   *
   * @return This object, unless overridden by subclass.
   */
  public XObject getFresh() {
    return this;
  }


  /**
   * Cast result object to a nodelist. Always issues an error.
   *
   * @return null
   */
  public NodeIterator nodeset() throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a NodeList!");

    return null;
  }

  /**
   * Cast result object to a nodelist. Always issues an error.
   *
   * @return null
   */
  public NodeList nodelist() throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a NodeList!");

    return null;
  }


  /**
   * Cast result object to a nodelist. Always issues an error.
   *
   * @return The object as a NodeSetDTM.
   */
  public NodeSetDTM mutableNodeset()
      throws javax.xml.transform.TransformerException {

    error(XPATHErrorResources.ER_CANT_CONVERT_TO_MUTABLENODELIST,
        new Object[]{getTypeString()});  //"Can not convert "+getTypeString()+" to a NodeSetDTM!");

    return (NodeSetDTM) m_obj;
  }

  /**
   * Cast object to type t.
   *
   * @param t Type of object to cast this to
   * @param support XPath context to use for the conversion
   * @return This object as the given type t
   */
  public Object castToType(int t, XPathContext support)
      throws javax.xml.transform.TransformerException {

    Object result;

    switch (t) {
      case CLASS_STRING:
        result = str();
        break;
      case CLASS_NUMBER:
        result = new Double(num());
        break;
      case CLASS_NODESET:
        result = iter();
        break;
      case CLASS_BOOLEAN:
        result = new Boolean(bool());
        break;
      case CLASS_UNKNOWN:
        result = m_obj;
        break;

      // %TBD%  What to do here?
      //    case CLASS_RTREEFRAG :
      //      result = rtree(support);
      //      break;
      default:
        error(XPATHErrorResources.ER_CANT_CONVERT_TO_TYPE,
            new Object[]{getTypeString(),
                Integer.toString(t)});  //"Can not convert "+getTypeString()+" to a type#"+t);

        result = null;
    }

    return result;
  }

  /**
   * Tell if one object is less than the other.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is less than the given object
   */
  public boolean lessThan(XObject obj2)
      throws javax.xml.transform.TransformerException {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.  Because the arguments
    // are backwards, we call the opposite comparison
    // function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.greaterThan(this);
    }

    return this.num() < obj2.num();
  }

  /**
   * Tell if one object is less than or equal to the other.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is less than or equal to the given object
   */
  public boolean lessThanOrEqual(XObject obj2)
      throws javax.xml.transform.TransformerException {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.  Because the arguments
    // are backwards, we call the opposite comparison
    // function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.greaterThanOrEqual(this);
    }

    return this.num() <= obj2.num();
  }

  /**
   * Tell if one object is greater than the other.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is greater than the given object
   */
  public boolean greaterThan(XObject obj2)
      throws javax.xml.transform.TransformerException {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.  Because the arguments
    // are backwards, we call the opposite comparison
    // function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.lessThan(this);
    }

    return this.num() > obj2.num();
  }

  /**
   * Tell if one object is greater than or equal to the other.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is greater than or equal to the given object
   */
  public boolean greaterThanOrEqual(XObject obj2)
      throws javax.xml.transform.TransformerException {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.  Because the arguments
    // are backwards, we call the opposite comparison
    // function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.lessThanOrEqual(this);
    }

    return this.num() >= obj2.num();
  }

  /**
   * Tell if two objects are functionally equal.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is equal to the given object
   */
  public boolean equals(XObject obj2) {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.equals(this);
    }

    if (null != m_obj) {
      return m_obj.equals(obj2.m_obj);
    } else {
      return obj2.m_obj == null;
    }
  }

  /**
   * Tell if two objects are functionally not equal.
   *
   * @param obj2 Object to compare this to
   * @return True if this object is not equal to the given object
   */
  public boolean notEquals(XObject obj2)
      throws javax.xml.transform.TransformerException {

    // In order to handle the 'all' semantics of
    // nodeset comparisons, we always call the
    // nodeset function.
    if (obj2.getType() == XObject.CLASS_NODESET) {
      return obj2.notEquals(this);
    }

    return !equals(obj2);
  }

  /**
   * Tell the user of an error, and probably throw an
   * exception.
   *
   * @param msg Error message to issue
   */
  protected void error(String msg)
      throws javax.xml.transform.TransformerException {
    error(msg, null);
  }

  /**
   * Tell the user of an error, and probably throw an
   * exception.
   *
   * @param msg Error message to issue
   * @param args Arguments to use in the message
   */
  protected void error(String msg, Object[] args)
      throws javax.xml.transform.TransformerException {

    String fmsg = XSLMessages.createXPATHMessage(msg, args);

    // boolean shouldThrow = support.problem(m_support.XPATHPROCESSOR,
    //                                      m_support.ERROR,
    //                                      null,
    //                                      null, fmsg, 0, 0);
    // if(shouldThrow)
    {
      throw new XPathException(fmsg, this);
    }
  }


  /**
   * XObjects should not normally need to fix up variables.
   */
  public void fixupVariables(java.util.Vector vars, int globalsSize) {
    // no-op
  }


  /**
   * Cast result object to a string.
   *
   *
   * NEEDSDOC @param fsb
   *
   * @return The string this wraps or the empty string if null
   */
  public void appendToFsb(com.sun.org.apache.xml.internal.utils.FastStringBuffer fsb) {
    fsb.append(str());
  }

  /**
   * @see com.sun.org.apache.xpath.internal.XPathVisitable#callVisitors(ExpressionOwner,
   * XPathVisitor)
   */
  public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) {
    assertion(false, "callVisitors should not be called for this object!!!");
  }

  /**
   * @see Expression#deepEquals(Expression)
   */
  public boolean deepEquals(Expression expr) {
    if (!isSameClass(expr)) {
      return false;
    }

    // If equals at the expression level calls deepEquals, I think we're
    // still safe from infinite recursion since this object overrides
    // equals.  I hope.
    if (!this.equals((XObject) expr)) {
      return false;
    }

    return true;
  }

}
